#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <sys/time.h>
#include <errno.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#include <unistd.h>

#define MAX_BUFFER 128
#define MAX_EPOLLSIZE (384 * 1024)
#define MAX_PORT 20

#define TIME_SUB_MS(tv1, tv2) ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)

int isContinue = 0;

// 将文件设置为非阻塞模式
static int ntySetNoblock(int fd) {
    int flags;
    flags = fcntl(fd, F_GETFL, 0);  // 获取fd的当前状态标志
    if (flags < 0) return flags; // 获取失败
    flags |= O_NONBLOCK;   // 将O_NONBLOCK设置进去
    if (fcntl(fd, F_SETFL, flags) < 0) return -1;
    return 0;
}

static int ntySetReUseAddr(int fd) {
    int reuse = 1;
    //SO_REUSEADDR 允许重用本地地址和端口。具体来说，它允许多个进程绑定到同一个地址和端口组合。通常用于在服务器关闭后，快速重启并重新绑定到相同的地址和端口
    return setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse));
}

int main(int argc, char **argv) {
    if (argc <= 2) {
        printf("Usage: %s ip port\n", argv[0]);
        exit(0);
    }

    const char *ip = argv[1];
    int port = atoi(argv[2]);
    int connections = 0;
    char buffer[128] = {0};
    int i = 0, index = 0;

    struct epoll_event events[MAX_EPOLLSIZE];
    int epoll_fd = epoll_create(MAX_EPOLLSIZE);

    strcpy(buffer, "Data From MulClient\n");

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(struct sockaddr_in));
    addr.sin_family = AF_INET;
    addr.sin_addr.s_addr = inet_addr(ip);

    struct timeval tv_begin;
    gettimeofday(&tv_begin, NULL);

    while(1) {
        if (++index >= MAX_PORT) index = 0;

        struct epoll_event ev;
        int sockfd = 0;

        if (connections < 340000 && !isContinue) {
            sockfd = socket(AF_INET, SOCK_STREAM, 0);
            if (sockfd == -1) {
                perror("socket");
                goto err;
            }
            addr.sin_port = htons(port + index);
            if (connect(sockfd, (struct sockaddr*)&addr, sizeof(struct sockaddr_in)) < 0) {
                perror("connect");
                goto err;
            }

            ntySetNoblock(sockfd);
            ntySetReUseAddr(sockfd);

            sprintf(buffer, "Hello Server: client -> %d\n", connections);
            send(sockfd, buffer, strlen(buffer), 0);

            ev.data.fd = sockfd;
            ev.events = EPOLLIN | EPOLLOUT;
            epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sockfd, &ev);

            connections++;
        }

        if (connections % 1000 == 999 || connections >= 340000) {
            struct timeval tv_cur;
            memcpy(&tv_cur, &tv_begin, sizeof(struct timeval));

            gettimeofday(&tv_begin, NULL);

            int time_used = TIME_SUB_MS(tv_begin, tv_cur);
            printf("connection: %d, sockfd: %d, time_used:%d\n", connections, sockfd, time_used);

            int nfds = epoll_wait(epoll_fd, events, connections, 100);
            for (i = 0; i < nfds; i++) {
                int clientfd = events[i].data.fd;
                if (events[i].events & EPOLLOUT) {
                    sprintf(buffer, "data from %d\n", clientfd);
                    send(sockfd, buffer, strlen(buffer), 0);
                } else if (events[i].events & EPOLLIN) {
                    char rBuffer[MAX_BUFFER] = {0};
                    ssize_t length = recv(sockfd, rBuffer, MAX_BUFFER, 0);
                    if (length > 0) {
                        printf("RecvBuffer:%s\n", rBuffer);

                        if (!strcmp(rBuffer, "quit")) {
                            isContinue = 0;
                        }
                    } else if (length == 0) {
                        printf(" Disconnect clientfd:%d\n", clientfd);
                        connections --;
                        close(clientfd);
                    } else {
                        if (errno == EINTR) continue;
                        printf("Error clientfd:%d errorno:%d\n", clientfd, errno);
                        close(clientfd);
                    }
                }
            }
        }
        usleep(500);
    }
    return 0;
err:
    printf("error :%s\n", strerror(errno));
    return 0;
}